﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Globalization;
using System.Web;
using AnyGrid.Properties;
using System.Web.Mvc;
using System.Collections;

namespace AnyGrid.Renderers
{
    internal abstract class JavaScriptElement
    {
        public static JavaScriptElement Create(object value)
        {
            if (value is JavaScriptElement)
            {
                return (JavaScriptElement)value;
            }
            var mm = ModelMetadataProviders.Current.GetMetadataForType(() => value, value.GetType());
            return Create(value, mm, 0);
        }

        internal static JavaScriptElement Create(object value, ModelMetadata metadata, int recursionDepth)
        {
            if (recursionDepth > 100)
            {
                throw new InvalidOperationException("Maximum recursion depth exceeded. Objects serialized to JSON cannot contain cycles.");
            }
            recursionDepth++;
            if (metadata.ModelType == typeof(int))
            {
                var intVal = Convert.ToInt32(metadata.Model, CultureInfo.InvariantCulture);
                return new JavaScriptLiteral(intVal);
            }
            else if (metadata.ModelType == typeof(long))
            {
                var intVal = Convert.ToInt64(metadata.Model, CultureInfo.InvariantCulture);
                return new JavaScriptLiteral(intVal);
            }
            else if (metadata.ModelType == typeof(string))
            {
                return new JavaScriptLiteral(metadata.Model.ToString());
            }
            else if (metadata.ModelType == typeof(bool))
            {
                var boolVal = Convert.ToBoolean(metadata.Model, CultureInfo.InvariantCulture);
                return new JavaScriptLiteral(boolVal);
            }
            else if (metadata.ModelType.IsArray)
            {
                var elements = (value as IEnumerable).Cast<object>().Select(e => JavaScriptElement.Create(e));
                return new JavaScriptArray(elements);
            }
            else if (metadata.Properties.Any())
            {
                return new JavaScriptObject(metadata, recursionDepth);
            }
            else
            {
                throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Type {0} not supported here.", metadata.ModelType));
            }
        }
    }

    internal class JavaScriptArray : JavaScriptElement
    {
        private readonly IList<JavaScriptElement> _members;

        public void Add(JavaScriptElement element)
        {
            _members.Add(element);
        }

        public override string ToString()
        {
            return "[" + string.Join(",", _members.Select(m => m.ToString())) + "]";
        }

        public JavaScriptArray() : this((IEnumerable<JavaScriptElement>)null) { }

        public JavaScriptArray(IEnumerable<JavaScriptElement> elements)
        {
            this._members = elements != null ? elements.ToList() : new List<JavaScriptElement>();
        }

        public JavaScriptArray(IEnumerable<string> elements)
        {
            this._members = new List<JavaScriptElement>();
            if (elements != null)
            {
                foreach (var element in elements)
                {
                    this._members.Add(new JavaScriptLiteral(element));
                }
            }
        }
    }

    internal class JavaScriptLiteral : JavaScriptElement
    {
        protected string LiteralValue { get; set; }

        public override string ToString()
        {
            return LiteralValue;
        }

        protected JavaScriptLiteral() { }

        public JavaScriptLiteral(bool value)
        {
            this.LiteralValue = value ? "true" : "false";
        }

        public JavaScriptLiteral(int value)
        {
            this.LiteralValue = value.ToString(CultureInfo.InvariantCulture);
        }

        public JavaScriptLiteral(long value)
        {
            this.LiteralValue = value.ToString(CultureInfo.InvariantCulture);
        }

        public JavaScriptLiteral(string value)
        {
            this.LiteralValue = HttpUtility.JavaScriptStringEncode(value, true);
        }
    }

    internal class JavaScriptMethodReference : JavaScriptLiteral
    {
        public JavaScriptMethodReference(string methodName)
        {
            this.LiteralValue = methodName;
        }
    }

    internal class JavaScriptObject : JavaScriptElement
    {
        private readonly IDictionary<string, JavaScriptElement> _members = new Dictionary<string, JavaScriptElement>();

        public void Add(string propertyName, JavaScriptElement propertyValue)
        {
            if (string.IsNullOrEmpty(propertyName))
            {
                throw new ArgumentNullException("propertyName");
            }
            var nameHierarchy = propertyName.Split(new[] { '.' });
            Add(this, nameHierarchy.ToList(), propertyValue);
        }

        private static void Add(JavaScriptObject obj, IList<string> names, JavaScriptElement value)
        {
            var currentName = names.First();
            if (names.Count > 1)
            {
                JavaScriptElement currentVal;
                var remainingNames = names.Skip(1);
                JavaScriptObject subObject;
                if (obj._members.TryGetValue(currentName, out currentVal))
                {
                    subObject = currentVal as JavaScriptObject;
                    if (currentVal == null)
                    {
                        throw new InvalidOperationException(string.Format(CultureInfo.CurrentUICulture, Resources.SubPropertyOfLiteral, string.Join(".", names.ToArray()), value));
                    }
                }
                else
                {
                    subObject = new JavaScriptObject();
                    obj._members.Add(currentName, subObject);
                }

                Add(subObject, remainingNames.ToList(), value);
            }
            obj._members.Add(currentName, value);
        }

        public override string ToString()
        {
            IEnumerable<string> propertyNamesAndValues = _members.Select(m => HttpUtility.JavaScriptStringEncode(m.Key, true) + ": " + m.Value.ToString());
            return "{" + string.Join(",", propertyNamesAndValues) + "}";
        }

        public JavaScriptObject() { }

        internal JavaScriptObject(ModelMetadata metadata, int recursionDepth) : this()
        {
            foreach (var prop in metadata.Properties)
            {
                if (prop.Model != null)
                {
                    Add(prop.PropertyName, JavaScriptLiteral.Create(prop.Model, prop, recursionDepth));
                }
            }
        }
    }
}
